/*
 * TownEvent.hpp
 *
 * Created on: Sep 13, 2013
 *     Author: jason
 */

#ifndef TOWNEVENT_HPP_
#define TOWNEVENT_HPP_

#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <SFML/Audio.hpp>
#include "../util.hpp"
#include "../ObjectManager.hpp"
#include "../../luaManager.hpp"
#include "../../Objects/Characters.hpp"

namespace OForce_Mode {

class Dialogue;

// classification for possible event types
enum EVENT_TYPE {
	INVALID_EVENT 					= 0,
	DIALOGUE_EVENT 					= 1,
	SCRIPTED_EVENT 					= 2,
	SCRIPTED_CHARACTER_EVENT 		= 3,
	LOOK_AT_EVENT 					= 4,
	MOVE_EVENT						= 5,
	ANIMATE_EVENT					= 6,
	JOIN_PARTY_EVENT				= 7,
	SOUND_EVENT						= 8,
	MAP_TRANSITION					= 9,
	SHOP_EVENT						= 10,
	YES_NO_EVENT					= 11
};

// class LinkEvent: handles linking of events 
// 		(e.g. play a sound while moving a sprite)
// Inputs: 
// 		id: identifier for event to be linked
//		concurrent: if true, start at the same time as parent, 
//					if false, start when parent finishes.
//		timer: how long to wait before event starts
class LinkEvent {
public: 
	LinkEvent(std::string id, bool concurrent, float delay) : 
		mId(id), mConcurrent(concurrent), timer(delay) {}
	~LinkEvent() {}
	std::string mId;
	bool mConcurrent;
	float timer;
};

// Base class from which all other events in TownMode are to be derived
class TownEvent {
friend class EventManager;
public:
	TownEvent(std::string id, EVENT_TYPE type) : mId(id), mType(type) {}
	virtual ~TownEvent() {}
	std::string getID() {return mId;}
	EVENT_TYPE getEventType() {return mType;}
	void addEventAtStart(std::string id) {addLinkEvent(id, true, 0);}
	void addEventAtStartWithDelay(std::string id, float delay) {addLinkEvent(id, true, delay);}
	void addEventAtEnd(std::string id) {addLinkEvent(id, false, 0);}
	void addEventAtEndWithDelay(std::string id, float delay) {addLinkEvent(id, false, delay);}
protected:
	void addLinkEvent(std::string id, bool concurrent, float delay) {
		vLink.push_back(LinkEvent(id, concurrent, delay));
	}
	virtual void start() = 0;
	virtual bool update() = 0;
	std::string mId;
	EVENT_TYPE mType;
	std::vector<LinkEvent> vLink;
}; 

// Base class for all character controlling events 
class CharacterEvent  : public TownEvent {
public:
	CharacterEvent(std::string id, EVENT_TYPE type, int charId);
	CharacterEvent(std::string id, EVENT_TYPE type, OForce_Object::Character *character);
	~CharacterEvent() {}
	OForce_Object::Character *getCharacter() { return mCharacter;}
	void end();
protected:
	OForce_Object::Character *mCharacter;
	virtual void start() = 0;
	virtual bool update() = 0;
};

// This class handles all events scripted solely in LUA.
// This is to be used when something other than common scripted functions 
// are needed (e.g. look here, move there et cetera)
class ScriptedCharacterEvent : public CharacterEvent {
public:
	ScriptedCharacterEvent(std::string id, int charId, std::string startFunc,
			std::string updateFunc);
	ScriptedCharacterEvent(std::string id, OForce_Object::Character *character,
			std::string startFunc, std::string updateFunc);
	~ScriptedCharacterEvent() {}
protected:
	void start();
	bool update();
	// Lua Functions
	luabind::object startFunction;
	luabind::object updateFunction;
}; 

class LookAtCharacterEvent : public CharacterEvent {
public:
	LookAtCharacterEvent(std::string id, int charId, int otherId);
	LookAtCharacterEvent(std::string id, OForce_Object::Character *character,
			OForce_Object::Object *otherObj);
	~LookAtCharacterEvent() {}
protected:
	// position to look at
	float x, y;
	OForce_Object::Object *objectToLookAt;
	void start();
	bool update();
};

// This class uses A-Star to move a character to a set position
class MoveCharacterEvent : public CharacterEvent {
public:
	MoveCharacterEvent(std::string id, int charId, float destX, float destY);
	MoveCharacterEvent(std::string id, OForce_Object::Character *character, float destX, float destY);
	~MoveCharacterEvent() {}
	void setDestination(float destX, float destY);
	std::vector<sf::Vector2f> getPath() { return mPath; }
	// if we need to end the movement early for some reason.
	void end();
protected:
	// position to go to
	float x, y;
	// previous location
	float prevX, prevY;
	// node location
	float nodeX, nodeY;
	// node index
	int node;
	// path from A-*
	std::vector<sf::Vector2f> mPath;
	void setDirection();
	void start();
	bool update();
};

// This class handles sprite non-standard (i.e. idle/walk) animations for a set time
class AnimateCharacterEvent : public CharacterEvent {
public:
	AnimateCharacterEvent(std::string id, int charId, std::string animationName,
		float animationDuration);
	AnimateCharacterEvent(std::string id, OForce_Object::Character *character, std::string animationName,
		float animationDuration);
	~AnimateCharacterEvent() {}
	void end();
protected:
	std::string mAnimName;
	float timer;
	OForce_Object::Character *mCharacter;
	void start();
	bool update();
};

class DialogueEvent : public TownEvent {
public:
	DialogueEvent(std::string id, int dialogueID);
	DialogueEvent(std::string id, Dialogue *dialogue);
	~DialogueEvent() {}
private:
	int mDialogueId;
	void start();
	bool update();
}; 

class SoundEvent : public TownEvent {
public:
	SoundEvent(std::string id, std::string soundFile);
	~SoundEvent() {mSound.stop(); }
	sf::Sound &getSound() {return mSound;}
private:
	void start() {mSound.play();}
	bool update();
	sf::SoundBuffer mSoundBuffer;
	sf::Sound mSound;
};

class MapTransitionEvent : public TownEvent {
public:
	MapTransitionEvent(std::string id, std::string newMap, std::string origin, bool f);
	~MapTransitionEvent() {}
private:
	// whether or not to fade.
	// I haven't decided how to do the world map yet but I might just do it as
	// a series of smaller maps which appear to have seemless transitions between them
	bool mFade;
	bool done;
	std::string mNewMap;
	std::string mOldMap;
	std::string mId;
	void start();
	bool update();
};

class EventManager {
public:
	EventManager() : updating(false) {}
	~EventManager();
	void update();
	void addEvent(TownEvent *event);
	// sets active event and launches it as well as its children
	void startEvent(std::string id);
	void startEvent(std::string id, float delay);
	void startEvent(TownEvent *event);
	void startEvent(TownEvent *event, float delay);
	// Event control
	void pauseEvent(std::string id);
	void resumeEvent(std::string id);
	// Control for a specific character
	void pauseAllEvents(OForce_Object::Character *character);
	void resumeAllEvents(OForce_Object::Character *character);
	void endAllEvents(OForce_Object::Character *character);
	// End specific event
	void end(std::string id, bool startLinks = true);
	void end(TownEvent *event, bool startLinks = true);
	// Determine if a specific event is active
	bool isEventActive(std::string id);
	bool hasActiveEvents() {return !activeEvents.empty();}
	bool hasDelayedEvents() {return !delayedEvents.empty();}
	TownEvent *getEvent(std::string id);
private:
	bool updating;
	// When an event starts or finishes, it must determine whether to 
	// initialize another event. 
	void processLinks(TownEvent *event, bool concurrent);
	std::unordered_map<std::string, TownEvent *> eventMap;
	std::vector<TownEvent *> activeEvents;
	std::vector<TownEvent *> pausedEvents;
	std::vector<std::pair<float, TownEvent *> > delayedEvents;
	std::vector<std::pair<float, TownEvent *> > pausedDelayedEvents;
};

} // namespace OForce_Mode

#endif /* TOWNMODEEVENT_HPP_ */
